﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace STM32F1_BootLoader_GUI
{

    public class Hex_Info
    {
        internal UInt32 Memery_Data_len { get; set; }
        internal UInt32 Valid_Data_len { get; set; }
        internal UInt32 Start_Address { get; set; }
        internal byte[] Data { get; set; }

        internal byte CheckSum 
        {
            get
            {
                byte Sum = 0;
                for(UInt32 i=0;i< Memery_Data_len; i++)
                {
                    Sum += Data[i];
                }
                return Sum;
            } 
        }
    }
    public class Hex_Record
    {
            internal int Type { get; set; }
            internal UInt32 DataLength { get; set; }
            internal UInt32 Address { get; set; }
            internal byte[] Data { get; set; }

            internal byte Checksum
            {
                get
                {
                    byte checksum;

                    checksum = (byte)DataLength;
                    checksum += (byte)Type;
                    checksum += (byte)Address;
                    checksum += (byte)((Address & 0xFF00) >> 8);

                    for (int i = 0; i < DataLength; i++)
                    {
                        checksum += Data[i];
                    }

                    checksum = (byte)(~checksum + 1);

                    return checksum;
                }
            }   
    }
    public class IntelHexParser
    {
        private const int START_CODE_OFFSET = 0;
        private const int START_CODE_LENGTH = 1;
        private const int BYTE_COUNT_OFFSET = START_CODE_OFFSET + START_CODE_LENGTH;
        private const int BYTE_COUNT_LENGTH = 2;
        private const int ADDRESS_OFFSET = BYTE_COUNT_OFFSET + BYTE_COUNT_LENGTH;
        private const int ADDRESS_LENGTH = 4;
        private const int RECORD_TYPE_OFFSET = ADDRESS_OFFSET + ADDRESS_LENGTH;
        private const int RECORD_TYPE_LENGTH = 2;
        private const int DATA_OFFSET = RECORD_TYPE_OFFSET + RECORD_TYPE_LENGTH;
        private const int CHECKSUM_LENGTH = 2;

        private static int MinimumLineLength
        {
            get
            {
                return START_CODE_LENGTH + BYTE_COUNT_LENGTH + ADDRESS_LENGTH + RECORD_TYPE_LENGTH + CHECKSUM_LENGTH;
            }
        }

        private static bool IsChecksumValid(String line, Hex_Record currentRecord)
        {
            byte current = Convert.ToByte(line.Substring((int)(DATA_OFFSET + currentRecord.DataLength * 2), CHECKSUM_LENGTH), 16);

            return currentRecord.Checksum == current;
        }


        private static Hex_Record[] Creat_Record_Array(String[] Souce_Text, UInt32 Sart_Address, UInt32 ignore_Line_Number)
        {
            UInt32 Address_H04 = 0;
            UInt32 Address_H02 = 0;
            UInt32 HEX_Real_Lenth = (UInt32)Souce_Text.Length;
            UInt32 line_Index = 0;
            List<Hex_Record> Record_Array = new List<Hex_Record>();

            foreach (String line in Souce_Text)
            {
                if (line_Index == Souce_Text.Length - ignore_Line_Number)
                    continue;
                line_Index++;
                if (line.Length < MinimumLineLength)
                {
                    throw new Exception("INVALID_LINE");
                }

                if (line[0] != ':')
                {
                    throw new Exception("INVALID_LINE");
                }
                Hex_Record Current = new Hex_Record();

                Current.DataLength = Convert.ToUInt16(line.Substring(BYTE_COUNT_OFFSET, BYTE_COUNT_LENGTH), 16);
                Current.Type = Convert.ToUInt16(line.Substring(RECORD_TYPE_OFFSET, RECORD_TYPE_LENGTH), 16);
                if (Current.Type == 5)
                {
                    HEX_Real_Lenth--;
                }
                else if (Current.Type == 4)
                {
                    HEX_Real_Lenth--;
                    Address_H04 = Convert.ToUInt16(line.Substring(DATA_OFFSET,(int)(Current.DataLength * 2)), 16);
                }
                else if (Current.Type == 3)
                {
                    HEX_Real_Lenth--;
                }
                else if (Current.Type == 2)
                {
                    HEX_Real_Lenth--;
                    Address_H02 = Convert.ToUInt16(line.Substring(DATA_OFFSET, (int)(Current.DataLength * 2)), 16);
                }
                else if (Current.Type == 1)
                {
                    HEX_Real_Lenth--;
                }
                else if (Current.Type == 0)
                {
                }

                Current.Data = new byte[Current.DataLength];
                for (int i = 0; i < Current.DataLength; i++)
                {
                    Current.Data[i] = Convert.ToByte(line.Substring(DATA_OFFSET + 2 * i, 2), 16);
                }
                if (Current.Type == 0)
                {
                    Current.Address = (Address_H04 << 16) + (Address_H02 << 4) + Convert.ToUInt16(line.Substring(ADDRESS_OFFSET, ADDRESS_LENGTH), 16);
                    if(Current.Address>= Sart_Address)
                        Record_Array.Add(Current);
                }
                else
                {
                    Current.Address = Convert.ToUInt16(line.Substring(ADDRESS_OFFSET, ADDRESS_LENGTH), 16);
                }
                if (!IsChecksumValid(line, Current))
                {
                    throw new Exception("INVALID_LINE");
                }
            }

            return Record_Array.ToArray();
        }
        private static Hex_Record[] Creat_Record_Array(String[] Souce_Text, UInt32 ignore_Line_Number)
        {
            UInt32 Address_H04 = 0;
            UInt32 Address_H02 = 0;
            UInt32 HEX_Real_Lenth = (UInt32)Souce_Text.Length;
            UInt32 line_Index = 0;
            List<Hex_Record> Record_Array = new List<Hex_Record>();

            foreach (String line in Souce_Text)
            {
                if (line_Index == Souce_Text.Length - ignore_Line_Number)
                    continue;
                line_Index++;
                if (line.Length < MinimumLineLength)
                {
                    throw new Exception("INVALID_LINE");
                }

                if (line[0] != ':')
                {
                    throw new Exception("INVALID_LINE");
                }
                Hex_Record Current = new Hex_Record();

                Current.DataLength = Convert.ToUInt16(line.Substring(BYTE_COUNT_OFFSET, BYTE_COUNT_LENGTH), 16);
                Current.Type = Convert.ToUInt16(line.Substring(RECORD_TYPE_OFFSET, RECORD_TYPE_LENGTH), 16);
                if (Current.Type == 5)
                {
                    HEX_Real_Lenth--;
                }
                else if (Current.Type == 4)
                {
                    HEX_Real_Lenth--;
                    Address_H04 = Convert.ToUInt16(line.Substring(DATA_OFFSET, (int)(Current.DataLength * 2)), 16);
                }
                else if (Current.Type == 3)
                {
                    HEX_Real_Lenth--;
                }
                else if (Current.Type == 2)
                {
                    HEX_Real_Lenth--;
                    Address_H02 = Convert.ToUInt16(line.Substring(DATA_OFFSET, (int)(Current.DataLength * 2)), 16);
                }
                else if (Current.Type == 1)
                {
                    HEX_Real_Lenth--;
                }
                else if (Current.Type == 0)
                {
                }

                Current.Data = new byte[Current.DataLength];
                for (int i = 0; i < Current.DataLength; i++)
                {
                    Current.Data[i] = Convert.ToByte(line.Substring(DATA_OFFSET + 2 * i, 2), 16);
                }
                if (Current.Type == 0)
                {
                    Current.Address = (Address_H04 << 16) + (Address_H02 << 4) + Convert.ToUInt16(line.Substring(ADDRESS_OFFSET, ADDRESS_LENGTH), 16);
                    Record_Array.Add(Current);
                }
                else
                {
                    Current.Address = Convert.ToUInt16(line.Substring(ADDRESS_OFFSET, ADDRESS_LENGTH), 16);
                }
                if (!IsChecksumValid(line, Current))
                {
                    throw new Exception("INVALID_LINE");
                }
            }

            return Record_Array.ToArray();
        }

        /*使用选择排序法，重排Record数组*/
        /*速度太慢，已经弃用*/
        private static void Select_Sort(Hex_Record[] Record)
        {
            //每次都从未排序的数据中查找一个最小值放到最前面
            for (UInt32 i = 0; i < Record.Length - 1; i++)
            {
                Hex_Record min = Record[i];
                for (UInt32 j = i + 1; j < Record.Length; j++)
                {
                    if (Record[j].Address < min.Address)
                    {
                        min = Record[j];
                        Hex_Record temp = Record[j];
                        Record[j] = Record[i];
                        Record[i] = temp;
                    }
                }
            }
        }

        /*使用快速排序法，重排Record数组*/
        private static void Quick_Sort(Hex_Record[] Record, int left, int right)
        {
            if (left < right)
            {
                int i = left;
                int j = right;
                UInt32 middle = Record[(left + right) / 2].Address;
                while (true)
                {
                    while (i < right && Record[i].Address < middle) { i++; };
                    while (j > 0 && Record[j].Address > middle) { j--; };
                    if (i == j) break;
                    Hex_Record temp = Record[i];
                    Record[i] = Record[j];
                    Record[j] = temp;
                    if (Record[i] == Record[j]) j--;
                }
                Quick_Sort(Record, left, i);
                Quick_Sort(Record, i + 1, right);
            }
        }

        /*遍历Record记录，对空白地址段填充数据*/
        private static void Record_data_filling(Hex_Record[] Record,byte data)
        {
            for (UInt32 i = 0; i < Record.Length; i++)
            {
                if (i < Record.Length-1)
                {
                    Int32 Diff_Lenth = (Int32)(Record[i+1].Address - Record[i].Address - Record[i].DataLength);
                    /* 如果本条记录的起始地址不等于上条记录的末地址，则进行填充*/
                    List<byte> Temp_Array = new List<byte>();
                    if (Diff_Lenth >=0)
                    {
                        Temp_Array = Record[i].Data.ToList();
                        for (UInt32 j = Record[i].DataLength; j < Record[i].DataLength+Diff_Lenth; j++)
                        {
                            Temp_Array.Add(data);
                        }
                        Record[i].Data = Temp_Array.ToArray();
                        Record[i].DataLength += (UInt32)Diff_Lenth;
                    }
                    else
                    {
                        throw new Exception("HEX_File ERROR");
                    }
                }
            }
        }


        private static void Record_data_filling(Hex_Record[] Record, byte dataL,byte dataH)
        {
            for (UInt32 i = 0; i < Record.Length; i++)
            {
                if (i < Record.Length - 1)
                {
                    Int32 Diff_Lenth = (Int32)(Record[i + 1].Address - Record[i].Address - Record[i].DataLength);
                    /* 如果本条记录的起始地址不等于上条记录的末地址，则进行填充*/
                    List<byte> Temp_Array = new List<byte>();
                    if (Diff_Lenth >= 0)
                    {
                        Temp_Array = Record[i].Data.ToList();
                        for (UInt32 j = Record[i].DataLength; j < Record[i].DataLength + Diff_Lenth; j++)
                        {
                            if(((Record[i].Address+ j)&1)==0)
                                Temp_Array.Add(dataL);
                            else
                                Temp_Array.Add(dataH);
                        }
                        Record[i].Data = Temp_Array.ToArray();
                        Record[i].DataLength += (UInt32)Diff_Lenth;
                    }
                    else
                    {
                        throw new Exception("HEX_File ERROR");
                    }
                }
            }
        }

        /*本重载有三个输入参数，
         * 参数1：hex路径
         * 参数2：app程序起始地址
         * 参数3：忽略hex文件末尾行数
         * 电容传感器专用
         用于从包含bootloader
        和app程序的hex源数据中提取出app程序部分*/
        /*正常解析hex请使用仅有hex路径一个参数的重载函数*/
        public static Hex_Info Hex_Parser(String Filepath, UInt32 Start_Address, UInt32 ignore_Line_Number)
        {
            /* 新建Hex_Info实例，用于存放最终的HEX信息*/
            Hex_Info Result_Out = new Hex_Info();
            Result_Out.Valid_Data_len = 0;
            Result_Out.Memery_Data_len = 0;
            StreamReader SReader = new StreamReader(Filepath, Encoding.Default);
            /*读取文本文件所有内容*/
            var Source = SReader.ReadToEnd();
            /*将HEX文本文件按换行符拆分*/
            String[] lines = Source.Split(Environment.NewLine.ToCharArray());
            /*去除Hex文件中的空行*/
            foreach (string line in lines)
            {
                if (line.Contains(":013FFF00AA17") == true)
                {
                    ignore_Line_Number += 1;
                }
            }
            lines = lines.Where(line => line.Length > 0).ToArray();
            /*将HEX每一个00记录的行，都解析进Record数组，通过解析04，02类型，记录每一行的真实物理地址和数据*/
            Hex_Record[] HEX_Record_Array = Creat_Record_Array(lines, Start_Address,ignore_Line_Number);

            /*根据真实物理地址，重新对Record记录数排序*/
            /*选择排序法太慢，弃用，使用快速排序*/
            //Select_Sort(HEX_Record_Array);
            Quick_Sort(HEX_Record_Array, 0, (int)(HEX_Record_Array.Length - 1));
            foreach (Hex_Record Record in HEX_Record_Array)
            {
                Result_Out.Valid_Data_len += Record.DataLength;
            }
            /* 遍历重排后的Hex记录，对空白地址段填充数据 */
            Record_data_filling(HEX_Record_Array, 0xFF, 0x3F);
            /*将HEX所有信息解析进Hex_Info类实例中存放*/
            Result_Out.Start_Address = HEX_Record_Array[0].Address;
            List<byte> Out_Array = new List<byte>();
            foreach (Hex_Record Record in HEX_Record_Array)
            {
                foreach (byte data in Record.Data)
                {
                    Out_Array.Add(data);
                }
                Result_Out.Memery_Data_len += Record.DataLength;
            }
            Result_Out.Data = Out_Array.ToArray();
            return Result_Out;
        }
        public static Hex_Info Hex_Parser(String Filepath)
        {
           /* 新建Hex_Info实例，用于存放最终的HEX信息*/
            Hex_Info Result_Out = new Hex_Info();
            Result_Out.Valid_Data_len = 0;
            Result_Out.Memery_Data_len = 0;
            StreamReader SReader = new StreamReader(Filepath, Encoding.Default);
            /*读取文本文件所有内容*/
            var Source = SReader.ReadToEnd();
            /*关闭数据流文件占用*/
            SReader.Close();
            /*将HEX文本文件按换行符拆分*/
            String[] lines = Source.Split(Environment.NewLine.ToCharArray());
            /*去除Hex文件中的空行*/
            lines = lines.Where(line => line.Length > 0).ToArray();
            /*将HEX每一个00记录的行，都解析进Record数组，通过解析04，02类型，记录每一行的真实物理地址和数据*/
            Hex_Record[] HEX_Record_Array = Creat_Record_Array(lines,0);
            /*根据真实物理地址，重新对Record记录数排序*/
            /*选择排序法太慢，弃用，使用快速排序*/
            //Select_Sort(HEX_Record_Array);
            Quick_Sort(HEX_Record_Array, 0, (int)(HEX_Record_Array.Length - 1));
            foreach (Hex_Record Record in HEX_Record_Array)
            {
                Result_Out.Valid_Data_len += Record.DataLength;
            }
            /* 遍历重排后的Hex记录，对空白地址段填充数据 */
            Record_data_filling(HEX_Record_Array, 0xFF);
            /*将HEX所有信息解析进Hex_Info类实例中存放*/
            Result_Out.Start_Address = HEX_Record_Array[0].Address;
            List<byte> Out_Array = new List<byte>();
            foreach (Hex_Record Record in HEX_Record_Array)
            {
                foreach (byte data in Record.Data)
                {
                    Out_Array.Add(data);
                }
                Result_Out.Memery_Data_len += Record.DataLength;
            }
            Result_Out.Data = Out_Array.ToArray();

           /* 将数据填充为4字节对齐*/
            Result_Out = Align_Address(Result_Out);
            return Result_Out;
        }

        private static Hex_Info Align_Address(Hex_Info Input)
        {
            UInt32 Fill_Num = 4 - (Input.Memery_Data_len % 4);
            if (Fill_Num % 4 != 0)
            {
                Input.Memery_Data_len += Fill_Num;
                List<byte> Out_Array = Input.Data.ToList<byte>();
                for (int i = 0; i < Fill_Num; i++)
                {
                    Out_Array.Add(0xFF);
                }
                Input.Data = Out_Array.ToArray();
            }
            return Input;
        }
    }
}
